<title>3D Orb Audio Visualization</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #000;
            font-family: Arial, sans-serif;
        }
        #controls {
            position: fixed;
            top: 20px;
            left: 20px;
            z-index: 10;
            color: white;
            background-color: rgba(0, 0, 0, 0.5);
            padding: 15px;
            border-radius: 10px;
            max-width: 350px;
            max-height: 80vh;
            overflow-y: auto;
        }
        button {
            background-color: #4CAF50;
            border: none;
            color: white;
            padding: 8px 16px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 14px;
            margin: 4px 2px;
            cursor: pointer;
            border-radius: 4px;
        }
        button:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
        }
        #stopBtn {
            background-color: #f44336;
        }
        #status {
            margin-top: 10px;
        }
        .form-group {
            margin-bottom: 15px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        input[type="text"], select, textarea {
            width: 100%;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
            box-sizing: border-box;
            background-color: rgba(255, 255, 255, 0.9);
        }
        textarea {
            height: 80px;
            resize: vertical;
        }
        .tab-container {
            margin-top: 15px;
        }
        .tab {
            overflow: hidden;
            border-bottom: 1px solid #ccc;
            margin-bottom: 10px;
        }
        .tab button {
            background-color: inherit;
            float: left;
            border: none;
            outline: none;
            cursor: pointer;
            padding: 8px 16px;
            transition: 0.3s;
            color: #ddd;
        }
        .tab button:hover {
            background-color: rgba(255, 255, 255, 0.1);
        }
        .tab button.active {
            background-color: rgba(255, 255, 255, 0.2);
            color: white;
        }
        .tabcontent {
            display: none;
            padding: 10px 0;
        }
        .error {
            color: #ff6b6b;
            font-weight: bold;
            margin-top: 10px;
        }
        /* Improved slider styling */
        .slider-container {
            margin-top: 10px;
            position: relative;
        }
        .slider-labels {
            display: flex;
            justify-content: space-between;
            margin-bottom: 5px;
            font-size: 12px;
            color: #ddd;
        }
        input[type="range"] {
            width: 100%;
            margin: 0;
            background: transparent;
            -webkit-appearance: none;
        }
        input[type="range"]:focus {
            outline: none;
        }
        input[type="range"]::-webkit-slider-runnable-track {
            width: 100%;
            height: 6px;
            cursor: pointer;
            background: rgba(255, 255, 255, 0.3);
            border-radius: 3px;
        }
        input[type="range"]::-webkit-slider-thumb {
            height: 16px;
            width: 16px;
            border-radius: 50%;
            background: #4CAF50;
            cursor: pointer;
            -webkit-appearance: none;
            margin-top: -5px;
        }
        input[type="range"]::-moz-range-track {
            width: 100%;
            height: 6px;
            cursor: pointer;
            background: rgba(255, 255, 255, 0.3);
            border-radius: 3px;
        }
        input[type="range"]::-moz-range-thumb {
            height: 16px;
            width: 16px;
            border-radius: 50%;
            background: #4CAF50;
            cursor: pointer;
        }
        .speed-value-display {
            position: relative;
            text-align: center;
            font-weight: bold;
            color: white;
            margin-top: 5px;
            font-size: 14px;
        }
    </style>
</head>
<body>

    <div id="controls">
        <h1>MLX-Audio Player</h1>
        
        <div class="tab">
            <button class="tablinks active" onclick="openTab(event, 'textToSpeech')">Text to Speech</button>
            <button class="tablinks" onclick="openTab(event, 'fileUpload')">File Upload</button>
        </div>
        
        <div id="textToSpeech" class="tabcontent" style="display: block;">
            <div class="form-group">
                <label for="text">Text to convert:</label>
                <textarea id="text" placeholder="Enter text here..."></textarea>
            </div>
            
            <div class="form-group">
                <label for="voice">Voice:</label>
                <select id="voice">
                    <option value="af_heart">AF Heart</option>
                    <option value="af_nova">AF Nova</option>
                    <option value="af_bella">AF Bella</option>
                    <option value="bf_emma">BF Emma</option>
                </select>
            </div>
            
            <div class="form-group">
                <label for="speed">Speech Speed:</label>
                <div class="slider-container">
                    <div class="slider-labels">
                        <span>Slower</span>
                        <span>Normal</span>
                        <span>Faster</span>
                    </div>
                    <input type="range" id="speed" min="0.5" max="2.0" step="0.1" value="1.0">
                    <div class="speed-value-display"><span id="speed-value">1.0</span>x</div>
                </div>
            </div>
            
            <button id="generateBtn">Generate Speech</button>
            <button id="openFolderBtn" style="background-color: #2196F3;">Open Output Folder</button>
            <div id="ttsError" class="error" style="display: none;"></div>
            <div id="ttsStatus" style="margin-top: 10px; max-width: 350px;"></div>
        </div>
        
        <div id="fileUpload" class="tabcontent">
            <input type="file" id="audioUpload" accept="audio/*">
            <div style="margin-top: 10px;">
                <button id="playBtn" disabled>Play</button>
                <button id="stopBtn" disabled>Stop</button>
            </div>
            <div id="status">Upload an audio file to begin visualization</div>
        </div>
    </div>

    <!-- Load Three.js from CDN -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>

    <script>
        // DOM elements
        const audioUpload = document.getElementById('audioUpload');
        const playBtn = document.getElementById('playBtn');
        const stopBtn = document.getElementById('stopBtn');
        const statusElement = document.getElementById('status');

        // TTS elements
        const textInput = document.getElementById('text');
        const voiceSelect = document.getElementById('voice');
        const speedInput = document.getElementById('speed');
        const speedValue = document.getElementById('speed-value');
        const generateBtn = document.getElementById('generateBtn');
        const openFolderBtn = document.getElementById('openFolderBtn');
        const ttsErrorElement = document.getElementById('ttsError');
        const ttsStatusElement = document.getElementById('ttsStatus');

        // Audio variables
        let audioContext;
        let analyser;
        let dataArray;
        let audioElement;
        let audioSource;

        // Three.js setup
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0x000000);
        document.body.appendChild(renderer.domElement);

        // Add orbit controls
        const controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        controls.dampingFactor = 0.05;

        // Position camera
        camera.position.set(0, 0, 100);
        camera.lookAt(0, 0, 0);

        // Add lights
        const ambientLight = new THREE.AmbientLight(0x404040);
        scene.add(ambientLight);

        const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
        directionalLight.position.set(1, 1, 1);
        scene.add(directionalLight);

        const pointLight = new THREE.PointLight(0xffffff, 1, 100);
        pointLight.position.set(0, 0, 0);
        scene.add(pointLight);

        // Create orb mesh
        const sphereGeometry = new THREE.IcosahedronGeometry(30, 4); // Higher detail icosahedron
        const sphereMaterial = new THREE.MeshPhongMaterial({
            color: 0x0088ff,
            emissive: 0x222222,
            shininess: 30,
            wireframe: false,
            flatShading: true
        });
        const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
        scene.add(sphere);

        // Store original vertex positions
        const originalVertices = [];
        for (let i = 0; i < sphereGeometry.attributes.position.count; i++) {
            originalVertices.push(
                new THREE.Vector3(
                    sphereGeometry.attributes.position.getX(i),
                    sphereGeometry.attributes.position.getY(i),
                    sphereGeometry.attributes.position.getZ(i)
                )
            );
        }

        // Create a glow effect
        const glowGeometry = new THREE.SphereGeometry(32, 32, 32);
        const glowMaterial = new THREE.MeshBasicMaterial({
            color: 0x0088ff,
            transparent: true,
            opacity: 0.15,
            side: THREE.BackSide
        });
        const glowMesh = new THREE.Mesh(glowGeometry, glowMaterial);
        scene.add(glowMesh);

        // Tab functionality
        function openTab(evt, tabName) {
            // Hide all tabcontent
            const tabcontent = document.getElementsByClassName("tabcontent");
            for (let i = 0; i < tabcontent.length; i++) {
                tabcontent[i].style.display = "none";
            }
            
            // Remove active class from all tablinks
            const tablinks = document.getElementsByClassName("tablinks");
            for (let i = 0; i < tablinks.length; i++) {
                tablinks[i].className = tablinks[i].className.replace(" active", "");
            }
            
            // Show the current tab and add active class to the button
            document.getElementById(tabName).style.display = "block";
            evt.currentTarget.className += " active";
        }
        
        // Speed slider update
        speedInput.addEventListener('input', function() {
            speedValue.textContent = this.value;
        });
        
        // Generate speech button handler
        generateBtn.addEventListener('click', function() {
            const text = textInput.value;
            const voice = voiceSelect.value;
            const speed = speedInput.value;
            
            if (!text.trim()) {
                showTtsError('Please enter some text');
                return;
            }
            
            // Hide previous error
            ttsErrorElement.style.display = 'none';
            ttsStatusElement.textContent = 'Generating speech...';
            
            // Create form data
            const formData = new FormData();
            formData.append('text', text);
            formData.append('voice', voice);
            formData.append('speed', speed);
            
            // Send request to server
            fetch('/tts', {
                method: 'POST',
                body: formData
            })
            .then(response => {
                if (!response.ok) {
                    return response.json().then(data => {
                        throw new Error(data.error || 'Failed to generate speech');
                    });
                }
                return response.json();
            })
            .then(data => {
                ttsStatusElement.textContent = 'Speech generated successfully!';
                
                // Clean up previous audio resources
                if (audioElement) {
                    audioElement.pause();
                    audioElement.removeAttribute('src');
                    audioElement.load();
                }
                
                if (audioSource) {
                    audioSource.disconnect();
                    audioSource = null;
                }
                
                // Create new audio element to avoid source node issues
                audioElement = new Audio();
                
                // Set audio source with absolute path
                audioElement.src = `/audio/${data.filename}`;
                audioElement.loop = false;
                
                // Enable play button
                playBtn.disabled = false;
                stopBtn.disabled = true;
                
                // Add ended event listener
                audioElement.addEventListener('ended', function() {
                    statusElement.textContent = "Audio finished playing.";
                    playBtn.disabled = false;
                    stopBtn.disabled = true;
                    resetSphere();
                });
                
                // Auto-play the generated audio
                playAudio();
            })
            .catch(error => {
                showTtsError(error.message);
                ttsStatusElement.textContent = '';
            });
        });
        
        // Open output folder button handler
        openFolderBtn.addEventListener('click', function() {
            fetch('/open_output_folder', {
                method: 'POST'
            })
            .then(response => {
                if (!response.ok) {
                    return response.json().then(data => {
                        throw new Error(data.error || 'Failed to open output folder');
                    });
                }
                return response.json();
            })
            .then(data => {
                ttsStatusElement.textContent = `Opened output folder: ${data.path}`;
            })
            .catch(error => {
                showTtsError(error.message);
            });
        });
        
        function showTtsError(message) {
            ttsErrorElement.textContent = message;
            ttsErrorElement.style.display = 'block';
        }
        
        // Function to play audio (reused for both upload and TTS)
        function playAudio() {
            if (!audioElement || !audioElement.src) {
                statusElement.textContent = "No audio available to play.";
                return;
            }
            
            statusElement.textContent = "Playing audio...";
            
            // Initialize audio context if needed
            if (!audioContext) {
                audioContext = new (window.AudioContext || window.webkitAudioContext)();
            }
            
            // Create analyser if needed
            if (!analyser) {
                analyser = audioContext.createAnalyser();
                analyser.fftSize = 256;
                dataArray = new Uint8Array(analyser.frequencyBinCount);
            }
            
            // Connect audio element to analyser if not already connected
            if (!audioSource) {
                try {
                    audioSource = audioContext.createMediaElementSource(audioElement);
                    audioSource.connect(analyser);
                    analyser.connect(audioContext.destination);
                } catch (error) {
                    console.error("Error connecting audio source:", error);
                    statusElement.textContent = "Error setting up audio visualization. Try refreshing the page.";
                    
                    // Still try to play the audio even if visualization fails
                    audioElement.play().catch(playError => {
                        statusElement.textContent = "Error playing audio: " + playError.message;
                    });
                    return;
                }
            }
            
            // Play audio
            audioElement.play().then(() => {
                playBtn.disabled = true;
                stopBtn.disabled = false;
            }).catch(error => {
                statusElement.textContent = "Error playing audio: " + error.message;
            });
        }

        // Handle audio upload
        audioUpload.addEventListener('change', function(e) {
            const file = e.target.files[0];
            if (!file) return;

            statusElement.textContent = "Audio file loaded. Press Play to start.";

            // Clean up previous audio resources
            if (audioElement) {
                audioElement.pause();
                audioElement.removeAttribute('src');
                audioElement.load();
            }
            
            if (audioSource) {
                audioSource.disconnect();
                audioSource = null;
            }
            
            // Create new audio element
            audioElement = new Audio();
            audioElement.src = URL.createObjectURL(file);
            audioElement.loop = false;

            // Enable play button
            playBtn.disabled = false;
            stopBtn.disabled = true;

            // Add ended event listener
            audioElement.addEventListener('ended', function() {
                statusElement.textContent = "Audio finished playing.";
                playBtn.disabled = false;
                stopBtn.disabled = true;
                resetSphere();
            });
        });

        // Play button handler
        playBtn.addEventListener('click', function() {
            playAudio();
        });

        // Stop button handler
        stopBtn.addEventListener('click', function() {
            if (audioElement) {
                audioElement.pause();
                audioElement.currentTime = 0;
                statusElement.textContent = "Audio stopped. Press Play to restart.";
                playBtn.disabled = false;
                stopBtn.disabled = true;

                // Reset sphere to original state
                resetSphere();
            }
        });

        // Reset sphere to original state
        function resetSphere() {
            const positionAttribute = sphereGeometry.attributes.position;

            for (let i = 0; i < positionAttribute.count; i++) {
                const originalVertex = originalVertices[i];
                positionAttribute.setXYZ(i, originalVertex.x, originalVertex.y, originalVertex.z);
            }

            positionAttribute.needsUpdate = true;
            sphereGeometry.computeVertexNormals();

            // Reset colors
            sphere.material.color.set(0x0088ff);
            sphere.material.emissive.set(0x222222);
            glowMesh.material.color.set(0x0088ff);
        }

        // Animation loop
        function animate() {
            requestAnimationFrame(animate);

            // Update controls
            controls.update();

            // Rotate sphere slowly
            sphere.rotation.y += 0.002;
            sphere.rotation.x += 0.001;
            glowMesh.rotation.copy(sphere.rotation);

            // Update visualization if audio is playing
            if (analyser && dataArray && !audioElement.paused) {
                analyser.getByteFrequencyData(dataArray);

                // Calculate average frequency values for different ranges
                const bassAvg = getAverageFrequency(dataArray, 0, 5);
                const midAvg = getAverageFrequency(dataArray, 6, 20);
                const trebleAvg = getAverageFrequency(dataArray, 21, 40);

                // Update sphere vertices based on frequency data
                const positionAttribute = sphereGeometry.attributes.position;

                for (let i = 0; i < positionAttribute.count; i++) {
                    const originalVertex = originalVertices[i];

                    // Calculate normalized distance from center (0-1)
                    const vertexLength = originalVertex.length();

                    // Get frequency value based on vertex position
                    let frequencyFactor;

                    // Use different frequency ranges based on vertex position
                    if (Math.abs(originalVertex.y) > vertexLength * 0.7) {
                        // Top/bottom vertices - use treble
                        frequencyFactor = trebleAvg / 255;
                    } else if (Math.abs(originalVertex.x) > vertexLength * 0.7) {
                        // Left/right vertices - use mids
                        frequencyFactor = midAvg / 255;
                    } else {
                        // Other vertices - use bass
                        frequencyFactor = bassAvg / 255;
                    }

                    // Scale vertex based on frequency
                    const scaleFactor = 1 + frequencyFactor * 0.5;

                    positionAttribute.setXYZ(
                        i,
                        originalVertex.x * scaleFactor,
                        originalVertex.y * scaleFactor,
                        originalVertex.z * scaleFactor
                    );
                }

                positionAttribute.needsUpdate = true;
                sphereGeometry.computeVertexNormals();

                // Update colors based on frequency
                const hue = (bassAvg / 255) * 0.3;
                const saturation = 0.8;
                const lightness = 0.4 + (midAvg / 255) * 0.2;

                sphere.material.color.setHSL(hue, saturation, lightness);
                sphere.material.emissive.setHSL(hue, saturation, lightness * 0.5);

                // Update glow
                glowMesh.material.color.setHSL(hue, saturation, lightness);
                glowMesh.scale.set(
                    1 + (bassAvg / 255) * 0.1,
                    1 + (bassAvg / 255) * 0.1,
                    1 + (bassAvg / 255) * 0.1
                );

                // Update point light
                pointLight.intensity = 0.5 + (bassAvg / 255) * 1.5;
                pointLight.color.setHSL(hue, saturation, lightness);
            }

            renderer.render(scene, camera);
        }

        // Helper function to get average frequency in a range
        function getAverageFrequency(dataArray, startIndex, endIndex) {
            let sum = 0;
            for (let i = startIndex; i <= endIndex; i++) {
                sum += dataArray[i];
            }
            return sum / (endIndex - startIndex + 1);
        }

        // Handle window resize
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });

        // Start animation loop
        animate();
    </script>
</body>
</html>